import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Scanner;

/**
 * This class implements the Chudnovsky algorithm for computing a precision of
 * pi defined by the user.
 *
 * @author Antti M. Harittu
 */
public class Pi {

    /**
     * The method returns a precision of pi containing d digits. For proper
     * functioning d > 0.
     *
     * @param d
     * @return
     */
    public static BigDecimal compute(int d) {

        /* The scale for computation */
        int s = (d < 991 ? 1000 : d + 10);

        /* A limit for iterations */
        int iMax = s / 14;

        /* Constant values */
        BigDecimal const1 = new BigDecimal(13591409);
        BigDecimal const2 = new BigDecimal(545140134);
        BigDecimal const3 = new BigDecimal(640320);
        BigDecimal const4 = new BigDecimal(426880);
        BigDecimal const5 = new BigDecimal(10005);

        /* Initial parameters */
        int min;
        int max;
        BigDecimal k;
        BigDecimal num;
        BigDecimal temp;
        BigDecimal fact = new BigDecimal(1);
        BigDecimal exp = new BigDecimal(1);
	BigDecimal a = new BigDecimal(1);
        BigDecimal b = new BigDecimal(0);

        for (int i = 1; i < iMax; i++) {
            k = new BigDecimal(i);

            min = i * 3 + 1;
            max = 6 * i;
            num = new BigDecimal(min);
            while (min < max) {
                min++;
                temp = new BigDecimal(min);
                num = num.multiply(temp);
            }

            for (int j = 0; j < 3; j++) {
                fact = fact.multiply(k);
                exp = exp.multiply(const3);
            }

            temp = num.divide(fact.multiply(exp), s, RoundingMode.DOWN);
            
            if (i % 2 == 0) {
                a = a.add(temp);
                b = b.add(temp.multiply(k));
            } else {
                a = a.subtract(temp);
                b = b.subtract(temp.multiply(k));
            }
        }

        return new BigDecimal((const4.multiply(sqrt(const5, s)).divide((const1
                .multiply(a).add(const2.multiply(b))), s, RoundingMode.DOWN))
                .toString().substring(0, d + 2));

    } // end compute

    /**
     * This method returns a scale-digit precision of the arguments' square root
     * using the Newton-method.
     *
     * @param b
     * @param scale
     * @return
     */
    public static BigDecimal sqrt(BigDecimal b, int scale) {

        /* Initial parameters */
        BigDecimal x2;
        BigDecimal x1 = new BigDecimal(100);
        BigDecimal half = new BigDecimal(0.5);

        /* A temporary scale. Rounded afterwards. */
        int s = scale + 10;

        do {
            x2 = x1.setScale(s, RoundingMode.HALF_DOWN);
            x1 = half.multiply(x1.add(b.divide(x1, s, RoundingMode.HALF_UP)));
            x1 = x1.setScale(s, RoundingMode.HALF_DOWN);
        } while (!x1.equals(x2));

        return new BigDecimal(x1.toString().substring(0, scale + 1));

    } // end sqrt

    /**
     * The method asks the user to input a desired number of digits of pi and
     * then calls compute()-method to return the precision. Digits are printed
     * through System.out.
     *
     * @param args
     */
    public static void main(String[] args) {
        int scale = 0;

        boolean b = true;
        while (b) {
            try {
                System.out.print("How many digits do you want to compute: ");
                Scanner scan = new Scanner(System.in);
                scale = scan.nextInt();
                b = false;
            } catch (Exception e) {
                System.out.println("An integer required.");
            }
        }

        if (scale < 1) {
            System.exit(0);
        }

        System.out.println("Computing...");
        System.out.println(compute(scale));

    } // end main
} // end class Pi